Skip to content

C# API review fixes#1343

Open
SteveSandersonMS wants to merge 34 commits into
mainfrom
stevesandersonms/stevesa-api-review-fixes
Open

C# API review fixes#1343
SteveSandersonMS wants to merge 34 commits into
mainfrom
stevesandersonms/stevesa-api-review-fixes

Conversation

@SteveSandersonMS
Copy link
Copy Markdown
Contributor

@SteveSandersonMS SteveSandersonMS commented May 20, 2026

Summary

API review fixes for the .NET SDK only. Sources combined into this PR:

  • ApiView comments classified into "SDK-side fixes" (the runtime-side fixes landed via copilot-agent-runtime#8283).
  • The api_review_csharp.md review doc.
  • Follow-on cleanups discovered while implementing the above.

Other SDKs are not touched here; once we are happy with the .NET shape we will port what makes sense.

What changed (by phase)

Phase Theme
3 Sealed every hand-written public type that does not need a derived class.
4a Simple property/method renames (e.g. spelling, casing, plurals).
4b Expanded abbreviations in SessionFsProvider.
4c Shape changes: Streaming flag, tools list shape, ToolBinaryResultType cleanup, misc.
4d/4e Removed the redundant CopilotClient.State enum; retyped LogLevel to a strongly-typed CopilotLogLevel value type.
4f Generic client.OnLifecycle<T> / session.On<T>; lifecycle events are now a typed polymorphic hierarchy (SessionCreatedEvent, etc.) instead of stringly-typed discriminators.
4g DateTimeOffset timestamps; PermissionRequestResult.Feedback cleanup.
5 Extracted SessionConfigBase between SessionConfig / ResumeSessionConfig; sealed all config classes.
6 Removed every hand-written named delegate type in favour of plain Func<...>.
7 SendAsync(string) / SendAndWaitAsync(string) convenience overloads.
8 README updates to match.
9 New RuntimeConnection discriminated config: replaces the flat CliPath/CliArgs/Port/UseStdio/CliUrl/TcpConnectionToken properties on CopilotClientOptions with a single Connection of type RuntimeConnection, constructed via factories: RuntimeConnection.Stdio(...), RuntimeConnection.Tcp(...), RuntimeConnection.Uri(...). Also renames ActualPort -> RuntimePort and CopilotHome -> BaseDirectory.

The test harness also got a small UX improvement: Ctx.CreateClient(useStdio: ..., options: ... { Connection = ... }) now throws eagerly if both are supplied. This caught a real bug introduced by Phase 9 bulk rewrite.

Notable thing we explicitly did NOT do: Phase 1

Phase 1 was originally going to retype every "opaque JSON" RPC field from object to System.Text.Json.JsonElement, on the theory that JsonElement is more honest about wire shape than object (which cannot be safely cast). The change was reverted on this branch (see commits 129c281e and 074ace71) for two reasons:

  1. The underlying problem is in the runtime schema, not the SDK. Several RPC parameters/results are polymorphic anyOf unions that have no idiomatic mapping in any of our SDK languages. Tracked in copilot-agent-runtime#8375: the schema generator should reject shapes that have no idiomatic mapping in C#/Go/Python/Rust, and require an explicit opt-in for cases that genuinely are meant to be opaque JSON. Once that lands, codegen will produce real typed hierarchies and the JsonElement workaround stops being needed.

  2. Phase 1 asymmetry created a worse consumer experience for inputs. Changing input parameters from object to JsonElement forced consumers to serialize themselves via their own JsonSerializerOptions, which silently emitted "headers": null and similar (because WhenWritingNull is not a default) and caused the runtime to reject otherwise-valid configs. On the previous object path, the SDK own JsonSerializerContext (which sets WhenWritingNull) was used, so this never bit consumers. We could have applied per-property [JsonIgnore(WhenWritingNull)] workarounds, but they are band-aids; the right fix is the schema change above. Reverting now keeps the API symmetric and aligned with main.

We will revisit JsonElement once #8375 lands and codegen can emit real types.

Risk / compatibility

This is a breaking-change PR by design. All E2E and unit tests have been updated in lock-step. The dotnet/samples and docs/ C# snippets have also been migrated.

Two issues filed against the runtime as follow-ups to this work:

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment thread dotnet/src/Client.cs Fixed
Comment thread dotnet/src/Client.cs

// Protocol v2 backward-compatibility adapters

public async ValueTask<ToolCallResponseV2> OnToolCallV2(string sessionId,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to maintain the V2 support?

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1343 · ● 946.3K

Comment thread dotnet/src/Client.cs
/// </code>
/// </example>
public IDisposable On(string eventType, Action<SessionLifecycleEvent> handler)
public IDisposable OnLifecycle<T>(Action<T> handler) where T : SessionLifecycleEvent
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: The lifecycle event subscription API has changed from two overloads (On(handler) and On(eventType, handler)) to a single generic method OnLifecycle<T>(handler). The other SDKs use a different pattern:

  • Node.js: client.on(handler) and client.on(eventType, handler)
  • Python: client.on(handler) and client.on(event_type, handler)
  • Go: client.On(handler) and client.OnEventType(eventType, handler)

The generic type approach is idiomatic .NET and has real advantages (compile-time type safety), but the method name OnLifecycle also diverges from On / on. If this design is retained, it's worth documenting that this is a .NET-specific pattern for filtering by event type, and considering whether the approach (if not the exact API shape) should influence the other SDKs.

Comment thread dotnet/src/Types.cs Outdated
/// When provided, the server will route <c>exitPlanMode.request</c> callbacks to this handler.
/// </summary>
public ExitPlanModeHandler? OnExitPlanMode { get; set; }
public ExitPlanModeHandler? OnExitPlanModeRequest { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: The property has been renamed from OnExitPlanMode to OnExitPlanModeRequest, but the equivalent property in the other SDKs still uses the original name (without the Request suffix):

  • Node.js: SessionConfig.onExitPlanMode
  • Python: create_session(..., on_exit_plan_mode=...)
  • Go: SessionConfig.OnExitPlanMode

If this rename is intentional as an improvement to the API, the same change should be considered for the other SDKs. If the goal is cross-SDK consistency, the other SDKs should eventually align with this new naming (or this property should revert to OnExitPlanMode).

Comment thread dotnet/src/Types.cs
/// This is used only when <see cref="CopilotClientOptions.SessionFs"/> is configured.
/// </summary>
public Func<CopilotSession, SessionFsProvider>? CreateSessionFsHandler { get; set; }
public Func<CopilotSession, SessionFsProvider>? CreateSessionFsProvider { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: This has been renamed from CreateSessionFsHandler to CreateSessionFsProvider, but all other SDKs use the ...Handler naming:

  • Node.js: SessionConfig.createSessionFsHandler
  • Python: create_session(..., create_session_fs_handler=...)
  • Go: SessionConfig.CreateSessionFsHandler

Interestingly, SessionFsProvider is already the name of the returned type in Node.js and Python too, so CreateSessionFsProvider could be seen as more descriptive. If this name is preferred, it may be worth aligning the other SDKs.

Comment thread dotnet/src/Types.cs
/// Default: false (resume event is emitted).
/// </summary>
public bool DisableResume { get; set; }
public bool SuppressResumeEvent { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: This has been renamed from DisableResume to SuppressResumeEvent, but:

  • Node.js: ResumeSessionConfig.disableResume
  • Go: ResumeSessionConfig.DisableResume
  • Python: Planned as disable_resume (TODO comment in source)

The new name SuppressResumeEvent is arguably more descriptive, but it diverges from the other SDK naming. If this naming is preferred, the other SDKs should be updated to match.

Comment thread dotnet/src/Types.cs Outdated
/// When provided, the server will route <c>autoModeSwitch.request</c> callbacks to this handler.
/// </summary>
public AutoModeSwitchHandler? OnAutoModeSwitch { get; set; }
public AutoModeSwitchHandler? OnAutoModeSwitchRequest { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: Same naming divergence as OnExitPlanModeRequest — the other SDKs use onAutoModeSwitch / on_auto_mode_switch / OnAutoModeSwitch (without the Request suffix). These two renames should be treated together if they're carried through to the other SDKs.

Comment thread dotnet/src/Session.cs Outdated
Comment thread dotnet/src/Types.cs
[JsonConverter(typeof(JsonStringEnumConverter<ConnectionState>))]
public enum ConnectionState
[DebuggerDisplay("{Value,nq}")]
public readonly struct CopilotLogLevel : IEquatable<CopilotLogLevel>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine if you think it's useful to maintain a separate type, but we're already using ILogger{Factory}... should we just use LogLevel here? Internally we can translate it to whatever the runtime needs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started with LogLevel but started thinking nothing stops the runtime from coming up with new loglevel values at any time that could have any semantics, without being constrained to things that match a .NET LogLevel. My guess is this won't inconvenience .NET devs too much since they aren't programmatically mapping some other loglevel into this in most cases, so they will still just pick whatever level they think is right. We could add an implicit conversion from LogLevel later if we want.

SteveSandersonMS and others added 12 commits May 20, 2026 15:14
Unmappable / arbitrary-JSON schema nodes now emit JsonElement instead of
object in both Generated/SessionEvents.cs and Generated/Rpc.cs. Internal
hand-written types that round-trip arbitrary JSON (ToolInvocation.Arguments,
ToolResultObject.ToolTelemetry, hook *Input* ToolArgs/ToolResult) are also
retyped to JsonElement. Hook *Output* fields, ElicitationSchema.Properties,
ElicitationResult.Content, and PermissionRequestResult.Rules remain object-
typed to keep consumer-side construction ergonomic; conversion happens at
the wire boundary inside the SDK.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Seal all hand-written leaf classes in Types.cs (70 classes). Kept
non-sealed:
- McpServerConfig: existing polymorphic abstract base
- SessionConfig / ResumeSessionConfig: will be refactored to share a base in Phase 5
- SessionLifecycleEvent: will become polymorphic base in Phase 4
- CopilotClientOptions / MessageOptions: existing protected Clone(...) ctor + virtual Clone() pattern
- SessionFsProvider: existing abstract base intended for consumer subclassing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CopilotSession.GetMessagesAsync -> GetEventsAsync
- (Resume)SessionConfig.OnExitPlanMode -> OnExitPlanModeRequest
- (Resume)SessionConfig.OnAutoModeSwitch -> OnAutoModeSwitchRequest
- (Resume)SessionConfig.CreateSessionFsHandler -> CreateSessionFsProvider
- ResumeSessionConfig.DisableResume -> SuppressResumeEvent
- ProviderConfig.MaxInputTokens -> MaxPromptTokens
- InputOptions -> UiInputOptions
- ISessionUiApi.ElicitationAsync -> ElicitAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per ApiView #278:
- RmAsync -> RemoveAsync
- MkdirAsync -> MakeDirectoryAsync
- ReaddirAsync -> ReadDirectoryAsync
- ReaddirWithTypesAsync -> ReadDirectoryWithTypesAsync

Applied to the consumer-facing protected abstract methods on
SessionFsProvider. The generated ISessionFsHandler interface keeps the
original RPC-style names; SessionFsProvider's explicit interface impls bridge
to the new spelled-out method names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nups)

- (Resume)SessionConfig.Streaming: bool -> bool? (JsonIgnore WhenWritingNull)
- McpServerConfig.Tools: IList<string> -> IList<string>? (null = include all,
  empty = include none); auto-init removed
- ToolBinaryResult.Type: bare string -> string-backed struct
  ToolBinaryResultType with well-known Image / Resource values
- Remove obsolete redirects:
  - CopilotClientOptions.GithubToken / .AutoRestart
  - PermissionRequestResultKind.DeniedInteractivelyByUser /
    DeniedCouldNotRequestFromUser / DeniedByRules
- Remove CopilotClientOptions.AutoStart (auto-start always on; consumer can
  still call StartAsync explicitly)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove CopilotClient.State and the ConnectionState enum (per ApiView
  decision: 'remove this property')
- Remove the _disconnected field that backed State
- Convert CopilotClientOptions.LogLevel from bare string with default 'info'
  to nullable CopilotLogLevel (string-backed struct in the same SDK house
  style as PermissionRequestResultKind/ToolBinaryResultType) with well-known
  values: None/Error/Warning/Info/Debug/All (matching the runtime's accepted
  LOG_LEVELS array). When null, the --log-level arg is omitted, which is
  equivalent to the runtime's 'default' value.
- Don't expose the values as an enum — the runtime doesn't necessarily treat
  them as a linear scale, so '<'/'>' comparisons would be misleading.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A PowerShell editing pipeline in earlier Phase 4 commits read UTF-8 source
bytes through a cp1252 decode, corrupting em-dashes (U+2014, UTF-8 E2 80 94)
into 'â€\u201d' sequences (UTF-8 C3 A2 E2 82 AC E2 80 9D). Inverse-encoded
the affected files via a small fix-mojibake.ps1 utility (in session state)
that handles UTF-8 BOMs correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add SessionCreatedEvent / SessionDeletedEvent / SessionUpdatedEvent /
  SessionForegroundEvent / SessionBackgroundEvent as sealed derived types
  of SessionLifecycleEvent. Unknown future event types deserialize to the
  base SessionLifecycleEvent for forward compatibility (consumers can still
  inspect Type).
- Remove the SessionLifecycleEventTypes static constants class (subsumed by
  the typed hierarchy).
- Replace CopilotClient.On / CopilotClient.On(string, ...) with a single
  generic OnLifecycle<T>(Action<T>) where T : SessionLifecycleEvent. Consumers
  pass a derived type to filter, or SessionLifecycleEvent to receive all.
- Replace CopilotSession.On(SessionEventHandler) with generic
  On<T>(Action<T>) where T : SessionEvent. Same filtering pattern.
- Remove the SessionEventHandler delegate; SessionConfig.OnEvent /
  ResumeSessionConfig.OnEvent are now Action<SessionEvent>?.
- Update samples, tests, and README.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add UnixMillisecondsDateTimeOffsetConverter for unix-millis-as-JSON-number
  timestamps
- Convert long Timestamp fields on hook *Input* types and PingResponse to
  DateTimeOffset via the new converter
- Convert SessionLifecycleEventMetadata.StartTime/ModifiedTime from ISO 8601
  strings to DateTimeOffset
- Convert SessionMetadata.StartTime/ModifiedTime from DateTime to DateTimeOffset
- Add PermissionRequestResult.Feedback property (parity with the RPC
  PermissionDecisionReject type's feedback field); forward to the wire only
  on reject decisions
- Tidy XML doc on PermissionRequestResult.Kind to reference the well-known
  PermissionRequestResultKind static members directly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rloads

Most SDK uses end up looking like SendAsync(new MessageOptions { Prompt = ... });
add a plain-prompt overload that wraps it. Per ApiView #258.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CopilotClientOptions.Cwd -> WorkingDirectory
- CopilotClientOptions.Remote -> EnableRemoteSessions
- McpStdioServerConfig.Cwd -> WorkingDirectory (wire name stays 'cwd')
- SessionListFilter.Cwd -> WorkingDirectory

Full CLI/server -> runtime terminology overhaul (the RuntimeConnection
discriminated config and removal of flat CliPath/CliArgs/Port/UseStdio/CliUrl
properties) is deferred — it requires substantial restructuring of the
connection-routing logic in Client.cs and warrants its own change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace stale references to SessionLifecycleEventTypes constants with the
new typed hierarchy (SessionCreatedEvent / SessionDeletedEvent / etc.).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SteveSandersonMS SteveSandersonMS force-pushed the stevesandersonms/stevesa-api-review-fixes branch from 9f16314 to a64803a Compare May 20, 2026 14:18
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1343 · ● 1.3M

Comment thread dotnet/src/Session.cs
/// </code>
/// </example>
public async Task<IReadOnlyList<SessionEvent>> GetMessagesAsync(CancellationToken cancellationToken = default)
public async Task<IReadOnlyList<SessionEvent>> GetEventsAsync(CancellationToken cancellationToken = default)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK inconsistency: GetEventsAsync vs getMessages

All other SDK implementations use the "Messages" naming for this method:

  • Node.js: session.getMessages() (nodejs/src/session.ts)
  • Python: session.get_messages() (python/copilot/session.py)
  • Go: session.GetMessages(ctx) (go/session.go)
  • Rust: GetMessagesResponse struct (rust/src/types.rs)

The .NET rename to GetEventsAsync diverges from the cross-SDK convention. Consider whether the other SDKs should also be updated to use "Events" naming, or whether this should remain GetMessagesAsync for consistency.

Comment thread dotnet/src/Types.cs Outdated
/// When provided, the server will route <c>exitPlanMode.request</c> callbacks to this handler.
/// </summary>
public ExitPlanModeHandler? OnExitPlanMode { get; set; }
public ExitPlanModeHandler? OnExitPlanModeRequest { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK inconsistency: OnExitPlanModeRequest vs onExitPlanMode

Other SDKs use onExitPlanMode (without the Request suffix):

  • Node.js: onExitPlanMode?: ExitPlanModeHandler (nodejs/src/types.ts)
  • Python: on_exit_plan_mode: ExitPlanModeHandler (python/copilot/session.py)
  • Go: OnExitPlanMode ExitPlanModeHandler (go/types.go)
  • Rust: on_exit_plan_mode method (rust/src/handler.rs)

The added Request suffix here (and for OnAutoModeSwitchRequest below) makes the semantics more explicit, but creates a naming divergence from all other language SDKs. If the "Request" suffix is the preferred direction, the other SDKs should be updated to match.

Comment thread dotnet/src/Types.cs
/// Default: false (resume event is emitted).
/// </summary>
public bool DisableResume { get; set; }
public bool SuppressResumeEvent { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK inconsistency: SuppressResumeEvent vs disableResume

Other SDKs all use disableResume / disable_resume:

  • Node.js: disableResume?: boolean (nodejs/src/types.ts)
  • Python: disable_resume: bool (python/copilot/session.py)
  • Go: DisableResume bool (go/types.go)
  • Rust: disable_resume: Option<bool> (rust/src/types.rs)

SuppressResumeEvent is semantically more descriptive, but it's a significant divergence from the cross-SDK disableResume convention. This is worth aligning across all SDKs if the new name is preferred.

Comment thread dotnet/src/Types.cs
/// </summary>
[JsonPropertyName("maxPromptTokens")]
public int? MaxInputTokens { get; set; }
public int? MaxPromptTokens { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK inconsistency: MaxPromptTokens vs maxInputTokens

Other SDKs expose this as maxInputTokens / MaxInputTokens in their public APIs (mapping to maxPromptTokens on the wire):

  • Node.js: maxInputTokens?: number in ProviderConfig (nodejs/src/types.ts:1676), mapped to maxPromptTokens wire format in client.ts
  • Go: MaxInputTokens int in ProviderConfig (go/types.go:991), tagged with json:"maxPromptTokens,omitempty"

The .NET SDK now exposes MaxPromptTokens directly, which aligns with the wire format but diverges from the other SDKs' public APIs. If MaxPromptTokens is the better name, consider updating Node.js and Go as well.

SteveSandersonMS and others added 2 commits May 20, 2026 15:26
- New abstract SessionConfigBase holds the 32 properties duplicated between
  SessionConfig and ResumeSessionConfig, plus the shared copy-constructor
  logic.
- SessionConfig now contains only SessionId + Cloud.
- ResumeSessionConfig now contains only SuppressResumeEvent + ContinuePendingWork.
- SessionConfig, ResumeSessionConfig, CopilotClientOptions, and MessageOptions
  are now sealed. Each has a private copy constructor + non-virtual Clone()
  method (was protected/virtual to allow subclassing — no longer a goal).
- Updated <see cref> references from SessionConfig.X to SessionConfigBase.X
  where the property lives on the base.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop these 13 named delegate types in favor of inline Func<...>:
- ToolHandler (unused; just deleted)
- PermissionRequestHandler -> Func<PermissionRequest, PermissionInvocation, Task<PermissionRequestResult>>
- UserInputHandler
- ExitPlanModeHandler
- AutoModeSwitchHandler
- CommandHandler -> Func<CommandContext, Task>
- ElicitationHandler -> Func<ElicitationContext, Task<ElicitationResult>>
- PreToolUseHandler / PostToolUseHandler / UserPromptSubmittedHandler
- SessionStartHandler / SessionEndHandler / ErrorOccurredHandler

Lambdas at call sites are unaffected (target-typed). The PermissionHandler
static class keeps its ApproveAll preset; the property type is now Func<...>.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

SteveSandersonMS and others added 2 commits May 20, 2026 16:03
Replaces the flat CliPath/CliArgs/Port/UseStdio/CliUrl/TcpConnectionToken
properties on CopilotClientOptions with a single Connection property of type
RuntimeConnection, constructed via factory methods:

- RuntimeConnection.Stdio(path?, args?) - spawns runtime over stdio (default)
- RuntimeConnection.Tcp(port=0, connectionToken?, path?, args?) - spawns
  runtime listening on TCP. port=0 auto-allocates; port-in-use is an error.
- RuntimeConnection.Uri(url, connectionToken?) - connects to an already
  running runtime; does not spawn.

The 'exactly one mode' invariant is now encoded by the type hierarchy
(ChildProcessRuntimeConnection abstract base for the spawning kinds, plus
UriRuntimeConnection for the connect-to-existing case). Connection token
only exists on Tcp/Uri (Stdio doesn't need one). Concrete subclasses have
internal ctors so factory methods are the only way to construct.

Client.cs stores the RuntimeConnection directly and pattern-matches on the
subtype at the connection-routing points (no separate flat fields).

Also:
- Rename CopilotClient.ActualPort -> RuntimePort.
- Rename CopilotClientOptions.CopilotHome -> BaseDirectory.
- README updated to describe the new Connection model and removed flags.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… removal

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread dotnet/src/Client.cs Fixed
Comment thread dotnet/src/Client.cs Fixed
SteveSandersonMS and others added 2 commits May 20, 2026 18:44
When a caller passes both, intent is ambiguous and easy to get wrong
(this was the source of the recent extension-test regression where the
useStdio parameter was silently ignored). Throw eagerly so the mistake
shows up immediately, and drop the now-redundant useStdio:false arg
from every call site that also sets Connection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1343 · ● 1.1M

Comment thread dotnet/src/Types.cs
/// </summary>
public ExitPlanModeHandler? OnExitPlanMode { get; set; }
/// <summary>Handler for exit-plan-mode requests from the server.</summary>
public Func<ExitPlanModeRequest, ExitPlanModeInvocation, Task<ExitPlanModeResult>>? OnExitPlanModeRequest { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: The .NET rename from OnExitPlanModeOnExitPlanModeRequest (and OnAutoModeSwitchOnAutoModeSwitchRequest on the next line) diverges from all other SDKs:

  • Node.js: onExitPlanMode / onAutoModeSwitch
  • Python: on_exit_plan_mode / on_auto_mode_switch
  • Go: OnExitPlanMode / OnAutoModeSwitch

If the *Request suffix is the correct semantic name (these are requests that the server sends to the client), it would be worth applying the same rename in the other SDKs too. If it's a .NET-only style choice, that's fine but worth documenting the intentional divergence.

Comment thread dotnet/src/Types.cs
/// This is used only when <see cref="CopilotClientOptions.SessionFs"/> is configured.
/// </summary>
public Func<CopilotSession, SessionFsProvider>? CreateSessionFsHandler { get; set; }
public Func<CopilotSession, SessionFsProvider>? CreateSessionFsProvider { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: CreateSessionFsProvider diverges from the name used in all other SDKs:

  • Node.js: createSessionFsHandler
  • Python: create_session_fs_handler
  • Go: CreateSessionFsHandler

If this rename from HandlerProvider is intentional (the field holds a factory for a SessionFsProvider, so "Provider" is semantically clearer), the same rename could be applied to the other SDKs for consistency.

Comment thread dotnet/src/Client.cs
/// Gets the actual TCP port the runtime is listening on, if using TCP transport.
/// </summary>
public int? ActualPort => _actualPort;
public int? RuntimePort => _actualPort;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: RuntimePort diverges from the name used in other SDKs:

  • Node.js: actualPort (private field — not exposed as a public property)
  • Python: actual_port (public property)
  • Go: ActualPort() (public method)

If RuntimePort is the preferred name going forward, the Python and Go SDKs could be updated. Node.js could also expose this publicly.

Comment thread dotnet/src/Session.cs
/// </code>
/// </example>
public IDisposable On(SessionEventHandler handler)
public IDisposable On<T>(Action<T> handler) where T : SessionEvent
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK opportunity: The new On<T>() generic method (where T : SessionEvent) allows callers to filter event subscriptions to a specific event type at compile time — a nice improvement. This capability doesn't currently exist in other SDKs:

  • Node.js: session.on(handler) — all events, no type filtering
  • Python: session.on(handler) — all events, no type filtering
  • Go: session.On(handler) — all events, no type filtering

Worth considering adding equivalent typed filtering to the other SDKs in a follow-up.

Comment thread dotnet/src/Types.cs
/// when connecting to an external runtime via <see cref="RuntimeConnection.Uri(string, string?)"/>.
/// </summary>
public bool Remote { get; set; }
public bool EnableRemoteSessions { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: EnableRemoteSessions diverges from the name used in all other SDKs:

  • Node.js: remote
  • Python: remote
  • Go: Remote

Similarly, BaseDirectory (line ~260) diverges from CopilotHome / copilotHome / copilot_home in the other SDKs, and WorkingDirectory at the client options level (line ~251) diverges from Cwd / cwd in Go, Node.js, and Python.

If these renames are considered improvements (e.g., EnableRemoteSessions is more self-documenting than Remote), it would be worth aligning the other SDKs in follow-up PRs.

Comment thread dotnet/src/Session.cs
/// <param name="prompt">The user message text.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>A task that resolves with the message ID.</returns>
public Task<string> SendAsync(string prompt, CancellationToken cancellationToken = default)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK opportunity: The new SendAsync(string prompt) and SendAndWaitAsync(string prompt) string overloads are a convenience improvement. Status across other SDKs:

  • Python: Already accepts prompt: str directly — consistent ✅
  • Go: Send(ctx, MessageOptions) requires a struct — no string shorthand
  • Node.js: send({ prompt: "..." }) requires an object — no string shorthand

Go and Node.js could benefit from adding equivalent string convenience overloads/variants.

SteveSandersonMS and others added 2 commits May 20, 2026 19:15
Replace CliPath/CliArgs/CliUrl/UseStdio with RuntimeConnection.Stdio/Tcp/Uri,
CopilotHome with BaseDirectory, McpStdioServerConfig.Cwd with WorkingDirectory,
GithubToken with GitHubToken, and add explicit type args to session.On() /
client.OnLifecycle() calls in C# code blocks across the docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace CliPath/CliUrl with RuntimeConnection.Stdio/Uri and add
explicit type args to session.On() across all C# scenario programs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SteveSandersonMS SteveSandersonMS changed the title Dotnet SDK: API review fixes C# API review fixes May 20, 2026
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1343 · ● 1.9M

Comment thread dotnet/src/Types.cs
/// Default: false (resume event is emitted).
/// </summary>
public bool DisableResume { get; set; }
public bool SuppressResumeEvent { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: SuppressResumeEvent diverges from the other SDK implementations, which all use DisableResume / disableResume / disable_resume:

  • Go: DisableResume bool (go/types.go:932)
  • Python: disable_resume: bool (python/copilot/session.py:1062)
  • Node.js: disableResume?: boolean (nodejs/src/types.ts:1593)

Worth aligning on a single canonical name if this is an intentional rename, or tracking a follow-up to update the other SDKs.

Comment thread dotnet/src/Session.cs
/// </code>
/// </example>
public async Task<IReadOnlyList<SessionEvent>> GetMessagesAsync(CancellationToken cancellationToken = default)
public async Task<IReadOnlyList<SessionEvent>> GetEventsAsync(CancellationToken cancellationToken = default)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: GetEventsAsync diverges from the other SDK implementations, which all use GetMessages / get_messages:

  • Go: func (s *Session) GetMessages(ctx context.Context) (go/session.go:1170)
  • Python: async def get_messages(self) (python/copilot/session.py:2217)
  • Node.js: async getMessages() (nodejs/src/session.ts:994)

If GetEvents is the preferred canonical name going forward, the other three SDKs would need follow-up updates.

Comment thread dotnet/src/Types.cs
/// </summary>
[JsonPropertyName("maxPromptTokens")]
public int? MaxInputTokens { get; set; }
public int? MaxPromptTokens { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: MaxPromptTokens diverges from the other SDKs' public ProviderConfig API, which all expose this as MaxInputTokens / maxInputTokens / max_input_tokens (each transparently remapping to the wire field maxPromptTokens):

  • Go: MaxInputTokens intjson:"maxPromptTokens" (go/types.go:991)
  • Python: max_input_tokens: int (mapped to wire maxPromptTokens in client.py:2533)
  • Node.js: maxInputTokens?: number (mapped to wire maxPromptTokens in client.ts:78)

Renaming only the .NET property creates an inconsistency. If MaxPromptTokens is the preferred name, the other SDKs should follow.

Comment thread dotnet/src/Client.cs
/// Gets the actual TCP port the runtime is listening on, if using TCP transport.
/// </summary>
public int? ActualPort => _actualPort;
public int? RuntimePort => _actualPort;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: RuntimePort diverges from the other SDKs, which use ActualPort / actual_port:

  • Go: func (c *Client) ActualPort() int (go/client.go:1300)
  • Python: def actual_port(self) -> int | None (python/copilot/client.py:1026)

If RuntimePort is the preferred name, consider updating the Go and Python equivalents in a follow-up.

Comment thread dotnet/src/Types.cs
/// This is used only when <see cref="CopilotClientOptions.SessionFs"/> is configured.
/// </summary>
public Func<CopilotSession, SessionFsProvider>? CreateSessionFsHandler { get; set; }
public Func<CopilotSession, SessionFsProvider>? CreateSessionFsProvider { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: CreateSessionFsProvider diverges from the other SDKs, which use createSessionFsHandler / create_session_fs_handler:

  • Node.js: createSessionFsHandler? in SessionConfig (nodejs/src/types.ts:1545)
  • Python: create_session_fs_handler: CreateSessionFsHandler (python/copilot/session.py:994)

This is a minor naming difference, but worth tracking for eventual alignment.

@SteveSandersonMS SteveSandersonMS marked this pull request as ready for review May 20, 2026 18:37
@SteveSandersonMS SteveSandersonMS requested a review from a team as a code owner May 20, 2026 18:37
Copilot AI review requested due to automatic review settings May 20, 2026 18:37
SteveSandersonMS and others added 2 commits May 20, 2026 19:40
GitHub.Copilot.SDK -> GitHub.Copilot (and all sub-namespaces in step).
The package name and csproj/.targets/.props filenames still include .SDK,
so RootNamespace is set explicitly on both csproj files to override the
default derived from the assembly name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR applies a large set of API-review-driven breaking changes to the .NET Copilot SDK, updating the public surface area (connection configuration, typed events, naming/casing, value types) and migrating tests, samples, and docs accordingly.

Changes:

  • Replaces flat transport options (CliPath/CliUrl/UseStdio/Port/TcpConnectionToken) with a discriminated RuntimeConnection (Stdio/Tcp/Uri) on CopilotClientOptions.
  • Introduces stronger typing and API reshaping (e.g., CopilotLogLevel, typed OnLifecycle<T> / session.On<T>, GetEventsAsync, ToolBinaryResultType, DateTimeOffset timestamps).
  • Updates all .NET tests, scenario programs, samples, and documentation to the new API shape.
Show a summary per file
File Description
test/scenarios/transport/tcp/csharp/Program.cs Updates scenario to use RuntimeConnection.Uri(...).
test/scenarios/transport/stdio/csharp/Program.cs Updates scenario to use RuntimeConnection.Stdio(...).
test/scenarios/transport/reconnect/csharp/Program.cs Updates reconnect scenario to RuntimeConnection.Uri(...).
test/scenarios/tools/virtual-filesystem/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/tools/tool-overrides/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/tools/tool-filtering/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/tools/skills/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/tools/no-tools/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/tools/mcp-servers/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/tools/custom-agents/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/sessions/streaming/csharp/Program.cs Updates connection config and typed event subscription (On<SessionEvent>).
test/scenarios/sessions/session-resume/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/sessions/infinite-sessions/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/sessions/concurrent-sessions/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/prompts/system-message/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/prompts/reasoning-effort/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/prompts/attachments/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/modes/minimal/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/modes/default/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/callbacks/user-input/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/callbacks/permissions/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/callbacks/hooks/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/bundling/fully-bundled/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/bundling/container-proxy/csharp/Program.cs Migrates to RuntimeConnection.Uri(...).
test/scenarios/bundling/app-direct-server/csharp/Program.cs Migrates to RuntimeConnection.Uri(...).
test/scenarios/bundling/app-backend-to-server/csharp/Program.cs Migrates to RuntimeConnection.Uri(...).
test/scenarios/auth/gh-app/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/auth/byok-openai/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/auth/byok-ollama/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/auth/byok-azure/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
test/scenarios/auth/byok-anthropic/csharp/Program.cs Migrates to RuntimeConnection.Stdio(...).
dotnet/test/Unit/SerializationTests.cs Updates serialization expectations for renamed/reshaped properties.
dotnet/test/Unit/PermissionRequestResultKindTests.cs Removes assertions for deprecated kind aliases.
dotnet/test/Unit/CloneTests.cs Updates cloning tests for Connection, renamed options, and renamed handlers.
dotnet/test/Unit/ClientSessionLifetimeTests.cs Updates client creation to use RuntimeConnection.Uri(...).
dotnet/test/Harness/TestHelper.cs Migrates helper logic to On<SessionEvent> and GetEventsAsync().
dotnet/test/Harness/E2ETestFixture.cs Updates shared client construction to RuntimeConnection.Tcp(...).
dotnet/test/Harness/E2ETestContext.cs Reworks harness client creation around Connection and tightens auth/token injection behavior.
dotnet/test/Harness/E2ETestBase.cs Updates multi-client resume flow to RuntimePort + RuntimeConnection.Uri(...).
dotnet/test/E2E/ToolsE2ETests.cs Updates binary tool result typing (ToolBinaryResultType).
dotnet/test/E2E/ToolResultsE2ETests.cs Migrates event subscriptions to typed On<SessionEvent>.
dotnet/test/E2E/SuspendE2ETests.cs Updates TCP/URI connection creation and RuntimePort usage.
dotnet/test/E2E/StreamingFidelityE2ETests.cs Migrates to On<SessionEvent> and GetEventsAsync().
dotnet/test/E2E/SessionMcpAndAgentConfigE2ETests.cs Renames MCP working directory property and updates config usage.
dotnet/test/E2E/SessionLifecycleE2ETests.cs Migrates persisted-history API to GetEventsAsync().
dotnet/test/E2E/SessionFsSqliteE2ETests.cs Renames session-fs factory to CreateSessionFsProvider and typed On<SessionEvent>.
dotnet/test/E2E/SessionFsE2ETests.cs Updates session-fs APIs, connection options, and event/history APIs.
dotnet/test/E2E/SessionE2ETests.cs Updates event APIs, typed subscriptions, and history retrieval naming.
dotnet/test/E2E/SessionConfigE2ETests.cs Updates event history API name and related assertions.
dotnet/test/E2E/RpcShellAndFleetE2ETests.cs Migrates polling helper to GetEventsAsync().
dotnet/test/E2E/RpcSessionStateE2ETests.cs Migrates persisted history retrieval to GetEventsAsync().
dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs Moves CliArgs usage into RuntimeConnection.Stdio(args: ...).
dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs Moves CliArgs usage into RuntimeConnection.Stdio(args: ...).
dotnet/test/E2E/RpcEventSideEffectsE2ETests.cs Migrates persisted history retrieval to GetEventsAsync().
dotnet/test/E2E/PermissionE2ETests.cs Migrates all subscriptions to typed On<SessionEvent>.
dotnet/test/E2E/PendingWorkResumeE2ETests.cs Updates TCP/URI client creation and typed subscriptions for pending-work resume.
dotnet/test/E2E/MultiTurnE2ETests.cs Migrates subscriptions to typed On<SessionEvent>.
dotnet/test/E2E/MultiClientE2ETests.cs Updates shared TCP server creation, URI client creation, and typed subscriptions.
dotnet/test/E2E/MultiClientCommandsElicitationE2ETests.cs Updates TCP/URI setup, typed subscriptions, and SuppressResumeEvent rename.
dotnet/test/E2E/ModeHandlersE2ETests.cs Renames mode-switch handlers and updates typed On<SessionEvent>.
dotnet/test/E2E/InMemorySessionFsSqliteHandler.cs Updates abstract method override names in session-fs provider.
dotnet/test/E2E/HookLifecycleAndOutputE2ETests.cs Updates hook timestamp assertions to DateTimeOffset.
dotnet/test/E2E/EventFidelityE2ETests.cs Migrates subscriptions and event-history retrieval to new APIs.
dotnet/test/E2E/ErrorResilienceE2ETests.cs Updates disposed-session history call to GetEventsAsync().
dotnet/test/E2E/ElicitationE2ETests.cs Renames UI method to ElicitAsync, input options to UiInputOptions, and removes named delegate usage.
dotnet/test/E2E/CompactionE2ETests.cs Migrates subscription to typed On<SessionEvent>.
dotnet/test/E2E/ClientOptionsE2ETests.cs Migrates to RuntimeConnection, RuntimePort, and renamed option properties/types.
dotnet/test/E2E/ClientLifecycleE2ETests.cs Migrates lifecycle subscriptions to OnLifecycle<T> and typed lifecycle event classes.
dotnet/test/E2E/ClientE2ETests.cs Migrates transport selection to RuntimeConnection factories.
dotnet/test/E2E/AbortE2ETests.cs Migrates subscriptions to typed On<SessionEvent>.
dotnet/test/ConnectionTokenTests.cs Migrates TCP/URI token plumbing to RuntimeConnection.
dotnet/src/UnixMillisecondsDateTimeOffsetConverter.cs Adds a converter for unix-ms timestamps to DateTimeOffset.
dotnet/src/Types.cs Major public API reshaping: connection config, log level value type, typed lifecycle events, renames, DateTimeOffset, tool binary type, config base extraction, sealing types.
dotnet/src/SessionFsProvider.cs Renames abstract methods to expanded names and seals result type.
dotnet/src/Session.cs Adds SendAsync(string)/SendAndWaitAsync(string) overloads, introduces On<T>, renames history to GetEventsAsync(), updates handler delegate types.
dotnet/src/PermissionHandlers.cs Updates built-in handler type to Func<...>-based signature.
dotnet/src/CopilotTool.cs Updates docs to reference SessionConfigBase.
dotnet/src/Client.cs Implements RuntimeConnection, typed lifecycle subscriptions, updated wire request shapes, and removes State surface.
dotnet/samples/ManualToolResume.cs Updates sample to typed On<SessionEvent>.
dotnet/samples/Chat.cs Updates sample to typed On<SessionEvent>.
dotnet/README.md Updates .NET SDK docs to new API surface (connection config, lifecycle APIs, event APIs, elicitation renames).
docs/troubleshooting/mcp-debugging.md Updates MCP stdio server working directory property name.
docs/troubleshooting/debugging.md Updates .NET example to use RuntimeConnection.Stdio(args: ...).
docs/setup/local-cli.md Updates .NET setup snippet to use RuntimeConnection.Stdio(path: ...).
docs/setup/github-oauth.md Corrects GithubToken casing to GitHubToken in docs.
docs/setup/backend-services.md Updates .NET backend-services snippets to RuntimeConnection.Uri(...).
docs/getting-started.md Updates .NET getting-started snippet to RuntimeConnection.Uri(...) and typed On<SessionEvent>.
docs/features/streaming-events.md Migrates examples to typed On<SessionEvent>.
docs/features/custom-agents.md Migrates examples to typed On<SessionEvent>.
docs/auth/authenticate.md Corrects GithubToken casing to GitHubToken in docs.

Copilot's findings

  • Files reviewed: 138/140 changed files
  • Comments generated: 6

Comment thread dotnet/README.md Outdated
Comment thread dotnet/src/Client.cs Outdated
Comment thread dotnet/src/Client.cs Outdated
Comment thread docs/setup/backend-services.md Outdated
Comment thread docs/setup/backend-services.md Outdated
Comment thread docs/getting-started.md Outdated
SteveSandersonMS and others added 3 commits May 20, 2026 19:48
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- README: fix Custom Permission Handler signature (PermissionInvocation, not ToolInvocation).
- Client.cs StartAsync: remove empty <para/> in remarks and the misleading
  'Reset disposed flag for restart scenarios' comment (no reset actually happens).
- Indent the Connection = RuntimeConnection.Uri(...) line inside object initializers
  in backend-services.md and getting-started.md C# snippets for consistency.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The validation extractor auto-injects a using directive into every C#
snippet; update it to match the new namespace.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread dotnet/src/Client.cs
{
continue;
}
try { subscription.Handler(evt); } catch { /* Ignore handler errors */ }
@github-actions
Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review

Thanks for the detailed PR description — the phased breakdown makes this easy to trace. Since this is explicitly a .NET-only API review pass with planned future ports, most of the changes are intentional divergences. The notes below are meant to serve as a reference for the "port what makes sense" follow-up work.


✅ Already consistent / no action needed

Change Status
Phase 4f – Generic typed session.On<T> / client.OnLifecycle<T> Node.js already has session.on(eventType, handler) with TypeScript generic typing; Go has client.On() + client.OnEventType(); Python uses annotated handlers. Semantically equivalent across all SDKs.
Phase 4d/4e – CopilotLogLevel rename .NET-specific to avoid collision with Microsoft.Extensions.Logging.LogLevel. No parallel needed in other SDKs.
Phase 5 – SessionConfigBase .NET class hierarchy concern. Python/Node.js/Go don't use inheritance here.
Phase 3/6 – Sealed types, Func<> over delegates .NET idiom.

📌 Items to track for future cross-SDK work

Phase 7 – SendAsync(string) / SendAndWaitAsync(string) convenience overloads

These are the most directly portable ergonomic improvements:

  • Python ✅ — already ergonomic: send(prompt: str, ...) takes prompt as a positional str arg, so this is covered.
  • Node.js ⚠️send(options: MessageOptions) and sendAndWait(options: MessageOptions) take only the options object. Simple callers must write session.send({ prompt: "Hello!" }) rather than session.send("Hello!").
  • Go ⚠️Send(ctx, options MessageOptions) similarly requires constructing a MessageOptions struct. A string-shorthand helper (e.g. SendPrompt(ctx, prompt string)) would be consistent with the .NET convenience.

Phase 9 – RuntimeConnection discriminated union

The .NET refactor moves from flat mutually-exclusive fields (CliPath/CliArgs/Port/UseStdio/CliUrl) to a sealed polymorphic factory (RuntimeConnection.Stdio(...), .Tcp(...), .Uri(...)). This makes invalid combinations impossible to express.

  • Node.js — still uses flat cliPath / cliUrl in CopilotClientOptions, with runtime panics/errors for invalid combos.
  • Python — still uses flat cli_path, use_stdio, cli_url, port fields, with runtime ValueError for invalid combos.
  • Go — still uses flat CLIPath, CLIUrl, UseStdio, Port, with a panic for invalid combos.

All three would benefit from the discriminated-union approach to make invalid config a compile-time (or at least early-init) error rather than a runtime surprise. That said, this is a breaking API change for those SDKs, so flagging for future consideration rather than blocking.


No blocking issues — the PR is well-scoped and the description is transparent about the intentional divergence. The two items above (Phase 7 convenience overloads for Node.js/Go, Phase 9 connection config shape) are worth filing follow-up tracking issues against if they aren't already captured elsewhere.

Generated by SDK Consistency Review Agent for issue #1343 · ● 1M ·

{
CliUrl = "localhost:4321",
UseStdio = false,
Connection = RuntimeConnection.Uri("localhost:4321"),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing a verb or a preposition. How about RuntimeConnection.ForUri?

Comment thread docs/setup/local-cli.md
var client = new CopilotClient(new CopilotClientOptions
{
CliPath = "/usr/local/bin/copilot",
Connection = RuntimeConnection.Stdio(path: "/usr/local/bin/copilot"),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly here, ForStdio or CreateWithStdio or something like that?

Comment thread docs/getting-started.md

```bash
dotnet add package GitHub.Copilot.SDK
dotnet add package GitHub.Copilot
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the package name changing?

public override ModelPickerCategory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
return new(GitHub.Copilot.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need the full qualification here, e.g.

Suggested change
return new(GitHub.Copilot.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));

<TargetFrameworks>net8.0;net10.0;netstandard2.0</TargetFrameworks>
<RootNamespace>GitHub.Copilot</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Version>0.1.0</Version>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we override this when publishing, but maybe we should tweak it to be clear that it's a local build, e.g. 0.0.0-dev or something like that

Comment thread dotnet/src/Types.cs
[JsonPropertyName("timestamp")]
public long Timestamp { get; set; }
[JsonConverter(typeof(UnixMillisecondsDateTimeOffsetConverter))]
public DateTimeOffset Timestamp { get; set; }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're inconsistent right now in whether the runtime sends down timestamps as milliseconds since epoch or as an iso datetime string. We should ideally consolidate on one or the other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants